#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <openssl/sha.h>
#include <pwd.h>


#include "PublicChain.h"
#include "cjson/cJSON.h"

#include "teex/base64.h"
#include "teex/util.h"
#include "teex/error.h"
#include "teex/const.h"
#include "teex/service.h"
#include "teex/debug.h"

int checkServiceInfo(teexService *serviceInfo)
{
	int i = 0;

	if (serviceInfo == NULL)
		return -1;
	if (!isNumberStr(serviceInfo->price))
		return -1;
	if (!isNumberStr(serviceInfo->workerFeerate))
		return -1;
	if (serviceInfo->desc == NULL)
		return -1;
	if (!isNumberStr(serviceInfo->providerAddr) 
			|| strlen(serviceInfo->providerAddr)!=PUBLIC_KEY_LEN)
		return -1;
	
	if (!isNumberStr(serviceInfo->publicKey) || strlen(serviceInfo->publicKey) != PUBLIC_KEY_LEN)
		return -1;
	if (!isNumberStr(serviceInfo->privateKey) || 
				strlen(serviceInfo->privateKey) != PRIVATE_KEY_LEN)
		return -1;

	if (serviceInfo->code == NULL)
		return -1;
	if (serviceInfo->codeEntry == NULL)
		return -1;
	if (serviceInfo->codeHash == NULL)
		return -1;
	if (serviceInfo->runtime == NULL)
		return -1;
	
	for (i = 0; i < serviceInfo->dataNumber; i++) {
		if (!isNumberStr(serviceInfo->dataList[i]) || strlen(serviceInfo->dataList[i]) != ID_LEN)
			return -1;
	}

	return 0;
}


teex_status_t createServiceOnChain(char *chainAddr, int chainPort,
									char *privateKey, char *publicKey,
									char *serviceContractAddr, char *tokenContractAddr,
									teexService *serviceInfo, char **serviceID,
									unsigned int timeout)
{
	if (chainAddr == NULL)
		return TEEX_ERROR_INVALID_CHAINURL;
	if (privateKey == NULL)
		return TEEX_ERROR_INVALID_PRIVATE_KEY;
	if (publicKey == NULL)
		return TEEX_ERROR_INVALID_PUBLIC_KEY;
	if (serviceContractAddr == NULL)
		return TEEX_ERROR_INVALID_CONTRACT1;
	if (tokenContractAddr == NULL)
		return TEEX_ERROR_INVALID_CONTRACT2;
	if (serviceInfo == NULL)
		return TEEX_ERROR_INVALID_SERVICEINFO;
	if (serviceID == NULL)
		return TEEX_ERROR_INVALID_SERVICEID_BUF;
	
	Service service, service_res;
	char chainURL[1024];
	int ret = 0;
	
	memset(chainURL, 0, 1024);
	sprintf(chainURL, "http://%s:%d", chainAddr, chainPort);

	PublicChain pc = PublicChain(chainURL, serviceContractAddr, tokenContractAddr,
									privateKey, publicKey);

	if (pc.status != 0) 
		return TEEX_ERROR_CHAIN_DISCONNECTED;

	if (checkServiceInfo(serviceInfo) < 0) 
		return TEEX_ERROR_INVALID_SERVICEINFO;
	
	strncpy(service.sp_addr, serviceInfo->providerAddr, PUBLIC_KEY_LEN);
	strncpy(service.service_pk, serviceInfo->publicKey, PUBLIC_KEY_LEN);
	
	memset(service.price, '0', PRICE_LEN);
	strncpy(service.price + PRICE_LEN - strlen(serviceInfo->price), 
					serviceInfo->price, strlen(serviceInfo->price));
	memset(service.worker_feerate, '0', PRICE_LEN);
	strncpy(service.worker_feerate + PRICE_LEN - strlen(serviceInfo->workerFeerate),
					serviceInfo->workerFeerate, strlen(serviceInfo->workerFeerate));

	service.desc = serviceInfo->desc;
	service.codeHash = serviceInfo->codeHash;
	service.data_num = serviceInfo->dataNumber;
	service.bound_data_list = serviceInfo->dataList;

	if (pc.newService(&service) != 0) {
		return TEEX_ERROR_CREATE_TASK;
	}

	while(timeout > 0 && ((ret = pc.getServiceByID(service.servideID, service_res)) != 0)) {
		timeout --;
		sleep(1);
	}

	if (ret != 0) 
		return TEEX_ERROR_TIMEOUT;
	
	*serviceID = strdup(service.servideID);
	return TEEX_SUCCESS;
}


teex_status_t generateMsgToManager(teexService *serviceInfo, char **msgToManager)
{
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;
	cJSON *serviceJson = NULL, *item = NULL;
	cJSON *array= NULL, *arrayItem = NULL;
	char priceBuf[PRICE_LEN + 1], workerFeerateBuf[PRICE_LEN + 1];
	char *msgBody = NULL, *msgBuf = NULL;
	int i = 0;

	if (serviceInfo == NULL || msgToManager == NULL)
		return TEEX_ERROR_INTERNAL;

	if ((serviceJson = cJSON_CreateObject()) == NULL) {
		return TEEX_ERROR_OUT_MEMORY;
	}
	
	cJSON_AddStringToObject(serviceJson, "service_id", serviceInfo->serviceID);
	cJSON_AddStringToObject(serviceJson, "address", serviceInfo->providerAddr);
	cJSON_AddStringToObject(serviceJson, "pub_key", serviceInfo->publicKey);
	cJSON_AddStringToObject(serviceJson, "prv_key", serviceInfo->privateKey);
	cJSON_AddStringToObject(serviceJson, "desc", serviceInfo->desc);
	
	memset(priceBuf, 0, PRICE_LEN + 1);
	memset(priceBuf, '0', PRICE_LEN);
	strncpy(priceBuf + PRICE_LEN - strlen(serviceInfo->price), 
					serviceInfo->price, strlen(serviceInfo->price));
	cJSON_AddStringToObject(serviceJson, "price", priceBuf);

	memset(workerFeerateBuf, 0, PRICE_LEN + 1);
	memset(workerFeerateBuf, '0', PRICE_LEN);
	strncpy(workerFeerateBuf + PRICE_LEN - strlen(serviceInfo->workerFeerate),
					serviceInfo->workerFeerate, strlen(serviceInfo->workerFeerate));
	cJSON_AddStringToObject(serviceJson, "worker_feerate", workerFeerateBuf);

	if ((item = cJSON_CreateObject()) == NULL) {
		retval = TEEX_ERROR_OUT_MEMORY;
		goto out;
	}
	if ((retval = fileToJson(serviceInfo->code, item)) != TEEX_SUCCESS)
		goto out;

	cJSON_AddItemToObject(serviceJson, "service_files", item);
	cJSON_AddStringToObject(serviceJson, "service_entry", serviceInfo->codeEntry);
	cJSON_AddStringToObject(serviceJson, "service_runtime", serviceInfo->runtime);

	if (serviceInfo->dataNumber > 0) {
		if ((array = cJSON_CreateArray()) == NULL) {
			retval = TEEX_ERROR_OUT_MEMORY;
			goto out;
		}

		for (i = 0; i < serviceInfo->dataNumber; i++) {
			if ((arrayItem = cJSON_CreateObject()) == NULL) {
				retval = TEEX_ERROR_OUT_MEMORY;
				cJSON_Delete(array);
				goto out;
			}

			cJSON_AddStringToObject(arrayItem, "data_id", serviceInfo->dataList[i]);
			cJSON_AddItemToArray(array, arrayItem);
		}

		cJSON_AddItemToObject(serviceJson, "data_list", array);
	}
	
	if ((msgBody = strdup(cJSON_Print(serviceJson))) == NULL) {
		retval = TEEX_ERROR_OUT_MEMORY;
		goto out;
	}

	if ((msgBuf = (char*)malloc(strlen(msgBody) + 128)) == NULL) {
		retval = TEEX_ERROR_OUT_MEMORY;
		goto out;
	}

	memset(msgBuf, 0, strlen(msgBody) + 128);
	sprintf(msgBuf, "PUT\nContent-Length: %d\n", strlen(msgBody));
	sprintf(msgBuf, "%s%s", msgBuf, msgBody);

	*msgToManager = msgBuf;

	retval = TEEX_SUCCESS;

out:
	if (msgBody)
		free(msgBody);
	if (serviceJson)
		cJSON_Delete(serviceJson);
	return retval;
	
}

teex_status_t getMsgFromManager(TEEX_SSL *ts, char **msgFromManager)
{
	if (ts == NULL || msgFromManager == NULL)
		return TEEX_ERROR_INTERNAL;
	
	char buf[1024], *msg = NULL;
	int msgLen = 0;

	memset(buf, 0, 1024);
	if (teexGetLine(ts, buf, 1024) <= 0) {
		return TEEX_ERROR_NETWORK;
	}

	if (sscanf(buf, "Content-Length: %d\n", &msgLen) != 1) {
		return TEEX_ERROR_INVALID_RESP_FORMAT;
	}

	if ((msg = (char*)malloc(msgLen + 1)) == NULL)
		return TEEX_ERROR_OUT_MEMORY;
	memset(msg, 0, msgLen + 1);

	if (TEEX_readn(ts, msg, msgLen) <= 0) {
		free(msg);
		return TEEX_ERROR_NETWORK;
	}

	*msgFromManager = msg;
	return TEEX_SUCCESS;


}

teex_status_t deployService(char *managerAddr, int managerPort,
								teexService *serviceInfo,
								char **errMsg)
{
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;

	if (managerAddr == NULL)
		return TEEX_ERROR_INVALID_MANAGER_ADDR;
	if (serviceInfo == NULL)
		return TEEX_ERROR_INVALID_SERVICEINFO;

	int managerFD = -1, tmpFD = -1;
	struct timeval timeout = {600, 0};
	TEEX_SSL *ts = NULL;

	char *home = NULL, *cert = NULL, *key = NULL;
	char *msgToManager = NULL, *msgFromManager = NULL;
	
	cJSON *item = NULL, *msgJson = NULL;

	if ((managerFD = teexConnect(managerAddr, managerPort)) < 0) {
		return TEEX_ERROR_CONNECT_MANAGER;
	}

	if (setsockopt(managerFD, SOL_SOCKET, SO_RCVTIMEO,
					(char*)&timeout, sizeof(struct timeval)) < 0) {
	
		retval = TEEX_ERROR_NETWORK;
		goto out;
	}

	if ((ts = TEEX_SSL_new(TEEX_SSL_SINGLE_RA_INITIATOR)) == NULL) {
		retval = TEEX_ERROR_OUT_MEMORY;
		goto out;
	}

	TEEX_SSL_set_fd(ts, managerFD);

	/* get the default client cert */
	if ((home = getpwuid(getuid())->pw_dir) == NULL) {
		retval = TEEX_ERROR_INVALID_CLIENT_CERT;
		goto out;
	}

	if ((cert = (char*)malloc(strlen(home) + 128)) == NULL) {
		retval = TEEX_ERROR_OUT_MEMORY;
		goto out;
	}
	memset(cert, 0, strlen(home) + 128);
	snprintf(cert, strlen(home) + 128, "%s/%s", home, ".teex/.private/client.crt");

	if ((key = (char*)malloc(strlen(home) + 128)) == NULL) {
		retval = TEEX_ERROR_OUT_MEMORY;
		goto out;
	}
	memset(key, 0, strlen(home) + 128);
	snprintf(key, strlen(home) + 128, "%s/%s", home, ".teex/.private/client.key");

	if ((tmpFD = open(cert, O_RDONLY)) < 0) {
		retval = TEEX_ERROR_INVALID_CLIENT_CERT;
		goto out;
	}
	close(tmpFD);
	tmpFD = 0;


	if ((tmpFD = open(key, O_RDONLY)) < 0) {
		retval = TEEX_ERROR_INVALID_CLIENT_CERT;
		goto out;
	}
	close(tmpFD);
	tmpFD = 0;

	/* setup the TEEX_SSL_connection */
	TEEX_SSL_set_ias_client_cert_key(ts, cert, key);
	TEEX_SSL_set_attributes(ts, 0);

	if (TEEX_connect(ts) != 0) {
		retval = TEEX_ERROR_ATTESTATION_FAILED;
		goto out;
	}
	
	if (checkServiceInfo(serviceInfo) < 0) {
		retval = TEEX_ERROR_INVALID_SERVICEINFO;
		goto out;
	}

	if (!isNumberStr(serviceInfo->serviceID) || strlen(serviceInfo->serviceID) != ID_LEN) {
		retval = TEEX_ERROR_INVALID_SERVICEINFO;
		goto out;
	}

	if ((retval = generateMsgToManager(serviceInfo, &msgToManager)) != TEEX_SUCCESS){
		goto out;
	}

	if (TEEX_writen(ts, msgToManager, strlen(msgToManager)) <= 0) {
		retval = TEEX_ERROR_NETWORK;
		goto out;
	}

	if ((retval = getMsgFromManager(ts, &msgFromManager)) != TEEX_SUCCESS) {
		goto out;
	}

	/* parse the msg and get the result */
	if ((msgJson = cJSON_Parse(msgFromManager)) == NULL) {
		retval = TEEX_ERROR_INVALID_JSON;
		goto out;
	}

	if ((item = cJSON_GetObjectItem(msgJson, "err_no")) != NULL
		&& cJSON_IsNumber(item)) {
		if (item->valueint == 0) {
			retval = TEEX_SUCCESS;
			goto out;
		} else {
			if ((item = cJSON_GetObjectItem(msgJson, "err_msg")) != NULL
				&& cJSON_IsString(item)) {
				*errMsg = strdup(item->valuestring);
				retval = TEEX_ERROR_MANAGER_INTERNAL;
				goto out;
			} else {
				retval = TEEX_ERROR_INVALID_JSON;
				goto out;
			}
		}
	} else {
		retval = TEEX_ERROR_INVALID_JSON;
		goto out;
	}

out:
	if (key)
		free(key);
	if (cert)
		free(cert);
	if (msgToManager)
		free(msgToManager);
	if (msgFromManager)
		free(msgFromManager);
	
	if (ts)
		TEEX_SSL_free(ts);
	if (msgJson)
		cJSON_Delete(msgJson);
	
	if (managerFD)
		close(managerFD);
	if (tmpFD)
		close(tmpFD);


	return retval;
}
